package dk.mirasola.systemtraining.bridgewidgets.shared.model;

import com.google.gwt.user.client.rpc.IsSerializable;

import java.util.HashMap;
import java.util.Map;

public class BiddingSequence implements IsSerializable {
    private static final Seat[] FORWARD_SEATS = new Seat[]{Seat.NORTH, Seat.EAST, Seat.SOUTH, Seat.WEST};
    private static final Seat[] REVERSE_SEATS = new Seat[]{Seat.WEST, Seat.SOUTH, Seat.EAST, Seat.NORTH};

    static class RoundSeat implements IsSerializable {
        private int round;
        private Seat seat;

        private RoundSeat(int round, Seat seat) {
            this.round = round;
            this.seat = seat;
        }

        public RoundSeat() {
        }

        public int getRound() {
            return round;
        }

        public Seat getSeat() {
            return seat;
        }

        public int getBidNumber() {
            int res = (round - 1) * 4;
            switch (seat) {
                case NORTH:
                    return res;
                case EAST:
                    return res + 1;
                case SOUTH:
                    return res + 2;
                case WEST:
                    return res + 3;
                default:
                    throw new RuntimeException("Unknow seat" + seat);
            }
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            RoundSeat roundSeat = (RoundSeat) o;

            if (round != roundSeat.round) return false;
            if (seat != roundSeat.seat) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = round;
            result = 31 * result + seat.hashCode();
            return result;
        }
    }

    public static class Builder {
        Seat dealer;
        boolean finish;
        RoundSeat lagest;
        Map<RoundSeat, Bid> bids = new HashMap<RoundSeat, Bid>();

        public Builder(Seat dealer, boolean finish) {
            if (dealer == null) {
                throw new NullPointerException();
            }
            this.dealer = dealer;
            this.finish = finish;
        }

        public Builder addBid(int round, Seat seat, Bid bid) {
            RoundSeat roundSeat = new RoundSeat(round, seat);
            bids.put(roundSeat, bid);
            if (lagest == null || lagest.getBidNumber() < roundSeat.getBidNumber()) {
                lagest = roundSeat;
            }
            return this;
        }

        public BiddingSequence create() {
            int roundCount = 0;
            Seat seatToBid = dealer;
            int roundToBid = 1;
            if (finish) {
                seatToBid = null;
                roundToBid = -1;
            }
            if (lagest != null) {
                roundCount = lagest.getRound();
                if (!finish) {
                    seatToBid = lagest.getSeat().getLHO();
                    if (seatToBid == Seat.NORTH) {
                        roundToBid = lagest.getRound() + 1;
                    } else {
                        roundToBid = lagest.getRound();
                    }
                }
            }

            return new BiddingSequence(dealer, bids, roundCount, roundToBid, seatToBid, finish);
        }
    }

    private Seat dealer = Seat.NORTH;
    private Map<RoundSeat, Bid> bids;
    private int roundCount = 0;
    private int roundToBid = 1;
    private Seat seatToBid = Seat.NORTH;
    private boolean finish = false;

    public BiddingSequence() {
    }

    public BiddingSequence(Seat dealer) {
        this.dealer = dealer;
        seatToBid = dealer;
        bids = new HashMap<RoundSeat, Bid>();
    }

    private BiddingSequence(Seat dealer, Map<RoundSeat, Bid> bids, int roundCount, int roundToBid, Seat seatToBid, boolean finish) {
        this.dealer = dealer;
        this.bids = bids;
        this.roundCount = roundCount;
        this.roundToBid = roundToBid;
        this.seatToBid = seatToBid;
        this.finish = finish;
    }

    public class BackwardIterator {
        Seat seat;
        int round;

        public BackwardIterator() {
            if (finish) {
                round = roundCount;
                for (Seat s : Seat.values()) {
                    if (getBid(round, s) != null) {
                        seat = s.getLHO();
                    }
                }
                if (seat.equals(Seat.NORTH))
                    round++;
            } else {
                seat = seatToBid;
                round = roundToBid;
            }
        }

        public Seat getSeat() {
            return seat;
        }

        public int getRound() {
            return round;
        }

        public boolean hasPrevBid() {
            return (round > 1 || !(seat.equals(getDealer())));
        }

        public Bid prevBid() {
            seat = seat.getRHO();
            if (seat.equals(Seat.WEST)) {
                round--;
            }
            return getBid(round, seat);
        }
    }

    /**
     * {@inheritDoc}
     */
    public final Seat getDealer() {
        return dealer;
    }

    /**
     * {@inheritDoc}
     */
    public final Seat getSeatInTurn() {
        return seatToBid;
    }

    /**
     * {@inheritDoc}
     */
    public final int getRoundInTurn() {
        return roundToBid;
    }

    /**
     * {@inheritDoc}
     */
    public final boolean isFinish() {
        return finish;
    }

    /**
     * {@inheritDoc}
     */
    public final int getRoundsCount() {
        return roundCount;
    }

    /**
     * {@inheritDoc}
     */
    public Bid getBid(int roundNumber, Seat seat) {
        return bids.get(new RoundSeat(roundNumber, seat));
    }

    /**
     * {@inheritDoc}
     */
    public void makeBid(Bid bid) {
        // check that argument bid not is null
        if (bid == null) {
            throw new IllegalArgumentException("method argument bid is null");
        }

        // check to see if bidding-sequence is finish
        if (finish) {
            throw new IllegalStateException("Bidding sequence has ended so no more bid can be made");
        }

        // is the bid legal at this point
        if (!isBidLegal(bid)) {
            throw new IllegalArgumentException("Bid " + bid + " is not legal at this point of biddingsequence");
        }

        // set bid
        bids.put(new RoundSeat(roundToBid, seatToBid), bid);

        // update finish?
        if (bid.equals(SpecialBid.PAS)) {
            // the bid just made is pas so is possible that the sequence is finish

            // figure out if the bidding-sequence have ended and updateBidablity finish
            int passes = 1; //number of passes in row

            BackwardIterator i = getBackwardIterator();
            while (i.hasPrevBid()) {
                Bid prevBid = i.prevBid();
                if (prevBid.equals(SpecialBid.PAS)) {
                    passes++;
                    if (passes == 3) {
                        // we got 3 passes in a row
                        // check that the situation not is p-p-p
                        if (i.hasPrevBid()) {
                            finish = true;
                        }
                    }
                } else {
                    // not a pas so sequence not ended
                    break;
                }
            }
        }

        if (seatToBid == Seat.NORTH || roundCount == 0) {
             // north just made a bide or first bid made so update round count
             roundCount++;
        }

         // update bidding in turn state
        if (finish) {
            // bidding sequence has ended
            seatToBid = null;
            roundToBid = -1;
        } else {
            // bidding sequence has not ended
            seatToBid = seatToBid.getLHO();
            if (seatToBid == Seat.NORTH) {
                // west has just made a bid so update round in turn
                roundToBid++;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void undoBid() {
        BackwardIterator i = getBackwardIterator();

        if (i.hasPrevBid()) {
            // we have a previous bid so remove it

            // move to previous bid
            i.prevBid();

            // set bid to void and updateData seat and round in turn
            bids.remove(new RoundSeat(i.getRound(), i.getSeat()));

            if (finish) {
                finish = false;
            }
            seatToBid = i.getSeat();
            roundToBid = i.getRound();
            if ((roundToBid == 1 && seatToBid == dealer) || (roundToBid > 1 && seatToBid == Seat.NORTH)) {
                roundCount--;
            }
        } else {
            // no previous bid - so its dealer turn an undo is not possible
            throw new IllegalStateException("dealer hasn't bid yet so undoing a bid isn't possible");
        }
    }

    public BackwardIterator getBackwardIterator() {
        return new BackwardIterator();
    }


    /**
     * {@inheritDoc}
     */
    public boolean isBidLegal(Bid bid) {
        // null is not legal
        if (bid == null) {
            return false;
        }

        if (finish) {
            return false;
        }

        // is it a special bid?
        if (bid instanceof SpecialBid) {
            SpecialBid specialBid = (SpecialBid) bid;
            if (specialBid.equals(SpecialBid.PAS)) {
                // pas is always legal
                return true;
            } else if (specialBid.equals(SpecialBid.DOUBLE)) {
                // figure out if double is legal
                return canDouble();
            } else if (specialBid.equals(SpecialBid.REDOUBLE)) {
                // figur out if redouble is legal
                return canRedouble();
            }
        }

        // is it a suitbid?
        if (bid instanceof ContractBid) {
            ContractBid contractBid = (ContractBid) bid;

            // find last ContractBid if exists
            ContractBid lastContractBid = getLastContractBid();

            // null means that we are only passes before us
            if (lastContractBid == null) {
                return true;
            } else {
                return contractBid.isHigher(lastContractBid);
            }
        }

        // default
        return false;
    }


    /**
     * {@inheritDoc}
     */
    public ContractBid getLastContractBid() {
        BackwardIterator i = getBackwardIterator();
        while (i.hasPrevBid()) {
            Bid bid = i.prevBid();
            if (bid instanceof ContractBid) {
                return (ContractBid) bid;
            }
        }
        return null;
    }

    /**
     * Figure out if its possible to redouble a the actual point of the biddingsequence
     */
    private boolean canRedouble() {
        BackwardIterator i = getBackwardIterator();
        if (i.hasPrevBid()) {
            Bid RHOBid = i.prevBid();
            if (RHOBid instanceof SpecialBid) {
                SpecialBid RHOSpecialBid = (SpecialBid) RHOBid;
                if (RHOSpecialBid.equals(SpecialBid.DOUBLE)) {
                    // rho has made a double so redouble the bastard
                    return true;
                } else if (RHOSpecialBid.equals(SpecialBid.PAS)) {
                    // rho has made a pas so check if partner has made
                    // a pas and lho a double
                    if (i.hasPrevBid()) {
                        Bid partnerBid = i.prevBid();
                        if (partnerBid.equals(SpecialBid.PAS)) {
                            // partner has made a pas so check lho
                            if (i.hasPrevBid()) {
                                Bid LHOBid = i.prevBid();
                                if (LHOBid.equals(SpecialBid.DOUBLE)) {
                                    // lho made a double so redouble the bastard
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }

        //default
        return false;
    }

    /**
     * Figure out if its possible to double a the actual point of the biddingsequence
     */
    private boolean canDouble() {
        BackwardIterator i = getBackwardIterator();
        if (i.hasPrevBid()) {
            Bid RHOBid = i.prevBid();
            if (RHOBid instanceof ContractBid) {
                // RHO made a suit so double the bastard
                return true;
            } else if (RHOBid.equals(SpecialBid.PAS)) {
                // RHOS made a pas so check if partner passed too a LHO made a ContractBid
                if (i.hasPrevBid()) {
                    Bid partnerBid = i.prevBid();
                    if (partnerBid.equals(SpecialBid.PAS)) {
                        // partner made a pas
                        if (i.hasPrevBid()) {
                            Bid LHOBid = i.prevBid();
                            if (LHOBid instanceof ContractBid) {
                                // LHO made at ContractBid so double the bastard
                                return true;
                            }
                        }
                    }
                }
            }
        }

        // default
        return false;
    }

    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (int i = 1; i <= roundCount; i++) {
            for (Seat seat : FORWARD_SEATS) {
                Bid bid = getBid(i, seat);
                if (bid != null) {
                    builder.append(bid + " ");
                } else {
                    builder.append("%");
                }

                if (seat != Seat.WEST) {
                    builder.append("-");
                }
            }
            if (i < roundCount) {
                builder.append(";");
            }
        }
        return builder.toString();
    }

    @Override
    public int hashCode() {
        return bids.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;

        if (obj instanceof BiddingSequence) {
            BiddingSequence other = (BiddingSequence) obj;
            return bids.equals(other.bids);
        }
        return false;
    }
}
